package com.yy.young.ums.client.filter;

import com.yy.young.base.util.GlobalConstants;
import com.yy.young.common.util.*;
import com.yy.young.interfaces.model.User;
import com.yy.young.interfaces.ums.model.SsoVerifyDTO;
import com.yy.young.interfaces.ums.service.IUmsOutService;
import com.yy.young.ums.client.util.CookieUtil;
import com.yy.young.ums.client.util.UmsClientConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by Administrator on 2017/5/9.
 */
public class SSOFilter implements Filter {

    private String[] allowUrls;//白名单,不需要登录的url,例如登入登出

    private static String REDIRECT_URL;//客户端重定向地址

    private static String SSO_VERIFY_URL;//单点验证地址

    private static Logger logger = LoggerFactory.getLogger(SSOFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        logger.info("[SSO单点登录过滤器],初始化...");
        String str = filterConfig.getInitParameter("allowUrls");
        allowUrls = str.split(";");
        logger.info("[SSO单点登录过滤器],白名单: {}", str);
        REDIRECT_URL = filterConfig.getInitParameter("redirectTo");
        if (StringUtils.isBlank(REDIRECT_URL)){
            //当web.xml没有配置时,采用默认转向地址
            REDIRECT_URL = UmsClientConstants.SSO_CLIENT.SSO_DEFAULT_REDIRECT_URL;
        }
        logger.info("[SSO单点登录过滤器],客户端重定向地址: {}", REDIRECT_URL);
        SSO_VERIFY_URL = filterConfig.getInitParameter("ssoVerifyUrl");
        if (StringUtils.isBlank(SSO_VERIFY_URL)){
            //当web.xml没有配置时,使用默认验证地址
            REDIRECT_URL = UmsClientConstants.SSO_CLIENT.SSO_VERIFY_URL;
        }
        logger.info("[SSO单点登录过滤器],单点登录验证地址: {}", SSO_VERIFY_URL);
        logger.info("[SSO单点登录过滤器],准备就绪!");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        try{
            //请求的url
            String requestUrl = ((HttpServletRequest)request).getRequestURI();//.getRequestURL().toString();
            //logger.info("[SSO单点登录过滤器],URL:"+requestUrl);
            //不需要登录的页面直接放过
            PathMatcher matcher = new AntPathMatcher();
            for(String url : allowUrls){
                if(matcher.match(url, requestUrl) || requestUrl.equals(REDIRECT_URL)){//匹配
                    logger.debug("[SSO单点登录过滤器] 白名单URL[{}],通过...", requestUrl);
                    chain.doFilter(request, response);//通过,继续执行过滤链
                    return;
                }
            }
            //使用无上下文匹配方式匹配白名单
            String context = ((HttpServletRequest) request).getContextPath();//获取当前上下文
            if(StringUtils.isNotBlank(context) && !"/".equals(context) && requestUrl.indexOf(context) == 0){
                String requestUrl2 = requestUrl.substring(context.length());
                for(String url : allowUrls){
                    if(matcher.match(url, requestUrl2) || requestUrl2.equals(REDIRECT_URL)){//匹配
                        logger.debug("[SSO单点登录过滤器] (无上下文匹配)白名单URL[{}],上下文为[{}],对应白名单项为[{}],通过...", requestUrl, context, url);
                        chain.doFilter(request, response);//通过,继续执行过滤链
                        return;
                    }
                }
            }

            //SSO单点验证
            if(verifySSO(request,response)){//验证通过
                logger.debug("[SSO单点登录过滤器] SSO验证成功[{}],通过...", requestUrl);
                chain.doFilter(request, response);//通过,继续执行过滤链
            }else{//失败后的处理,跳转到登录页
                logger.info("[SSO单点登录过滤器] SSO验证失败[{}],跳转到登录页...", requestUrl);
                redirect2LoginPage((HttpServletRequest)request,(HttpServletResponse)response);
            }
            return;
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void destroy() {
        logger.info("[SSO单点登录过滤器],销毁...");
        allowUrls = null;
    }


    //SSO验证
    private static boolean verifySSO(ServletRequest request, ServletResponse response) throws Exception{
        if(!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)){
            throw new ServletException("SSO单点过滤器只处理HTTP请求!");
        }else{
            HttpServletRequest req = (HttpServletRequest)request;//http请求
            HttpServletResponse res = (HttpServletResponse)response;
            //先从session中获取用户信息,避免每次都向用户中心请求
            User loginUser = (User)req.getSession().getAttribute(GlobalConstants.SESSION.KEY_LOGINUSER);
            //从cookie中取token信息
            Cookie cookie = CookieUtil.getCookie((HttpServletRequest) request, UmsClientConstants.SSO_CLIENT.TOKEN_KEY);

            //如果cookie中不存在token,尝试从request中取token,兼容cookie被禁或外部系统访问等情况
            String token = cookie != null ? cookie.getValue() : request.getParameter(UmsClientConstants.SSO_CLIENT.TOKEN_KEY);
            /*
             * 存在session且session中的用户令牌与当前请求中携带的令牌一致时,我们认为当前请求是已登录的
             * 防止因为用户在其他子系统中退出后其他的子系统无法同步更新退出状态
             * 需要保证用户退出后token(cookie)在所有系统中都被清空,否则无法保证用户在一处下线,处处下线
             */
            if(loginUser != null && cookie != null && loginUser.getToken() != null && loginUser.getToken().equals(cookie.getValue())) {
                logger.debug("[SSO单点登录过滤器] 用户session与cookie存储的用户token匹配,验证通过!");
                return true;
            }else if(loginUser != null && token != null && loginUser.getToken() != null && loginUser.getToken().equals(token)) {
                logger.debug("[SSO单点登录过滤器] 用户session与token匹配,验证通过!");
                return true;
            }else if(loginUser != null && cookie == null && StringUtils.isBlank(token)){
                //当单机环境时,不存在token令牌,只要有loginUser就算通过
                logger.debug("[SSO单点登录过滤器] 用户session有效(单机环境),验证通过!");
                return true;
            }else{//session失效
                /**
                 * TODO 当前应用session中没有信息,此时需要向ums服务端发起验证登录请求,流程如下:
                 * 1.首先,存在一个token(由服务端生成,不存在则直接跳转登录),该token存储可以在用户cookie中进行保存,此时需要对cookie跨域作处理
                 * 2.向服务端发起验证请求,由服务端验证token有效性,此时服务端应该根据自身的规则来校验此token并且将结果返回
                 * 3.当服务端返回结果为验证失败,则跳转登录;当服务端返回成功(成功的情况下服务端应当将用户信息返回到客户端)
                 * 4.将返回的用户信息存入应用session中,返回true
                 */
                //HttpClientHelper.postData("", "");

                //存在token令牌,则进行验证
                if (StringUtils.isNotBlank(token)){

                    //TODO 如果使用redis存储token,可以直接在redis取当前令牌的信息

                    //由服务端控制token时,通过对服务端发起验证请求来验证token的有效性

                    //使用dubbo方式,适用于内部子系统,返回的是SsoVerifyDTO对象格式
                    IUmsOutService umsOutService = SpringContextHolder.getBean("umsOutService");
                    SsoVerifyDTO result = null;
                    if (umsOutService != null){//ums外部服务有效,则直接调用服务(可能是本地服务或者是redis服务)
                        result = umsOutService.verifySSO(token);
                    }else if (StringUtils.isNotBlank(SSO_VERIFY_URL)){//ums外部服务不存在且单点验证URL有效,则使用http方式进行验证
                        //http请求的方式,适用于外部系统,返回的是SsoVerifyDTO的json格式
                        String str = HttpClientHelper.postData(SSO_VERIFY_URL, "token=" + token);
                        if (StringUtils.isNotBlank(str) && !"error".equals(str)){
                            result = CommonJsonUtil.jsonStr2Bean(str, SsoVerifyDTO.class);
                        }
                    }else {

                    }


                    //对验证结果进行校验
                    if (result != null && result.getCode() == 1){//验证成功
                        String userId = result.getUserId();//验证返回的当前用户id
                        if (StringUtils.isNotBlank(userId)){
                            //用户信息
                            User user = result.getUser();//umsOutService.getLoginUserById(userId);
                            if (user != null){
                                //用户有效,存入session
                                logger.info("[SSO单点登录过滤器] 成功验证token({})并取得当前用户信息,存入session! 用户信息为: {}", token, user);
                                if(user.getToken() == null){
                                    user.setToken(token);//记录token,保留登录令牌,实现免登
                                }
                                ((HttpServletRequest) request).getSession().setAttribute(GlobalConstants.SESSION.KEY_LOGINUSER, user);
                                return true;
                            }
                        }
                    }else{
                        logger.warn("[SSO单点登录过滤器] token验证失败,token={},验证结果={}", token, result);
                    }

                }else{//无令牌
                    logger.debug("[SSO单点登录过滤器] 验证失败,当前请求没有携带令牌!");
                }

                return false;//单机版直接返回false
            }
        }
    }

    /**
     * 重定向到登录页
     * 1.页面请求,直接重定向到登陆页
     * 2.ajax请求,返回特定格式的结果,需要在页面ajax全局处理中作对应的捕获处理
     */
    private static void redirect2LoginPage(HttpServletRequest request,HttpServletResponse response) throws Exception{
        String requestWith = request.getHeader("x-requested-with");  //ajax XMLHttpRequest
        String contentType = request.getHeader("x-content-type");  //除了xml，其他都返回josn格式
        String contextPath = request.getContextPath();
        contentType = (contentType == null)? "":contentType;
        if(requestWith==null){    //浏览器访问
            String redirectUrl = REDIRECT_URL;// + "?redirectTo="+request.getRequestURL();
            response.sendRedirect(redirectUrl);
            //response.sendRedirect(UmsClientConstants.SSO_CLIENT.REDIRECT_LOGIN + "?redirectTo="+request.getRequestURL());
        }else{ //ajax返回
            if(contentType.equalsIgnoreCase("xml")){ //返回xml
                response.setHeader("Content-Type", "text/xml;charset=UTF-8");
                response.setCharacterEncoding("UTF-8");
                response.getWriter().print("<root><data_id></data_id><data_desc></data_desc><code>-101</code><info>未登录或会话过期！</info><data></data><callback></callback><redirectTo>"+REDIRECT_URL+"</redirectTo></root>");
            }else{//返回josn
                response.setHeader("Content-Type", "text/json;charset=UTF-8");
                response.setCharacterEncoding("UTF-8");
                response.getWriter().print("{\"data_id\":\"\",\"data_desc\":\"\",\"code\":-101,\"info\":\"未登录或会话过期！\",\"data\":[],\"callback\":\"\",\"redirectTo\":\""+REDIRECT_URL+"\"}");
            }
        }
    }

}
